#include <EB0/EB0.h>

namespace EB0
{
namespace Window
{
#pragma region Layer Compare (used by RenderWindow)
	// Determine the drawing order for layers. Return true if drawn before the other, false if after.
	// Higher z-index objects are to be drawn later.
	bool Layer::compare(const Layer& lhs, const Layer& rhs)
	{
		// Lower or equal z-index?
		if (lhs.z <= rhs.z)
			return true;

		// Draw the first layer after the second
		return false;
	}

	// Compare given two pointers
	bool Layer::comparePtr(const Layer* lhs, const Layer* rhs)
	{
		// Check if pointers are valid
		if (!lhs)
			return true; // Place the first object before the second

		if (!rhs)
			return false; // Place the second object before the first

		return compare(*lhs, *rhs);
	}
#pragma endregion

#pragma region Constructor / Destructor
	// Default Constructor
	Layer::Layer(RenderWindow* window, const sf::FloatRect& viewRect, unsigned int zindex)
		: sf::View(viewRect)
	{
		mWindow = window;
		z = zindex;
		mDirtyRenderableList = false;
	}

	// Constructor with center and half-size
	Layer::Layer(RenderWindow* window, const sf::Vector2f& center, const sf::Vector2f& halfSize, unsigned int zindex)
		: sf::View(center, halfSize)
	{
		mWindow = window;
		z = zindex;
		mDirtyRenderableList = false;
	}

	// Destructor
	Layer::~Layer()
	{
		// Remove all renderables
		removeAllRenderables();

		// Reset values
		z = 0;
		mDirtyRenderableList = false;
	}
#pragma endregion

#pragma region Get Window
	// Get Window pointer
	RenderWindow* Layer::getWindow() const
	{
		return mWindow;
	}
#pragma endregion

#pragma region Rendering
	// Render Scene
	void Layer::render(RenderWindow& target) const
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Use this view for the render target
		target.SetView(*(sf::View*)this);

		// Render all objects in the scene
		mDirtyRenderableList = false; // only necessary during traversal

		// Sort list of renderables by the way they will be displayed
		mRenderables.sort(Graphics::Renderable::comparePtr);

		// Traverse through each renderable
		static RenderableList::iterator renderableIter; // frequently created otherwise, for traversing
		static RenderableSet completedRenderables; // set of completed renderables, for additional checking on dirty sets
		static bool dirtyRenderableFlag = false; // we need only check the completedRenderables list when the set has been dirtied

		if (!mRenderables.empty())
		{
			renderableIter = mRenderables.begin();
			completedRenderables.clear(); // clear the array of called renderables
			dirtyRenderableFlag = false; // no need to do the extra checking... yet

			while (renderableIter != mRenderables.end())
			{
				// Has this renderable already been drawn?
				if (dirtyRenderableFlag && completedRenderables.find(*renderableIter) != completedRenderables.end())
				{
					// Skip it
					renderableIter++;
					continue;
				}

				// Add the renderable to the already completed list
				completedRenderables.insert(*renderableIter);

				// Draw the renderable if it's in view
				if ((*renderableIter)->inView(*(sf::View*)this))
					target.Draw(**renderableIter);

				// Has the renderable set been altered?
				if (mDirtyRenderableList)
				{
					// Go to the beginning of the list if there are still remaining renderables
					if (!mRenderables.empty())
						renderableIter = mRenderables.begin();

					// Reset the flag as the situation is now being taken care of
					mDirtyRenderableList = false;
					dirtyRenderableFlag = true; // we'll need to perform additional checking now..

					// Resort the list, even though this will have no effect on previously drawn objects...
					mRenderables.sort(Graphics::Renderable::comparePtr);
				}
				else
					renderableIter++;
			}
		}

		// Reset the view to default for the render target
		// as we don't want objects given to this view...
		target.SetView(target.GetDefaultView());
	}
#pragma endregion

#pragma region Renderable Management
	// Attach Renderable
	void Layer::attachRenderable(Graphics::Renderable* renderable)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Only allow a renderable to exist once in the list.
		// Duplicates would go unnoticed anyway save for transparency.
		bool found = false;
		RenderableList::iterator renderIter;

		for (renderIter = mRenderables.begin(); renderIter != mRenderables.end() && !found; renderIter++)
			found = (renderable == *renderIter);

		if (!found)
		{
			mRenderables.push_back(renderable);
			mDirtyRenderableList = true;
		}
	}

	// Remove Renderable
	void Layer::removeRenderable(Graphics::Renderable* renderable)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Remove all instances of this renderable, assuming only one instance ever exists
		if (!mRenderables.empty())
		{
			RenderableList::iterator renderIter;

			for (renderIter = mRenderables.begin(); renderIter != mRenderables.end(); renderIter++)
			{
				if (renderable == *renderIter)
				{
					mRenderables.erase(renderIter);
					mDirtyRenderableList = true;
					return;
				}
			}
		}
	}

	// Remove all renderables
	void Layer::removeAllRenderables()
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		mRenderables.clear();
		mDirtyRenderableList = true;
	}
#pragma endregion
} // namespace Window
}